# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##
"""
Config option dialogs for tile/tk.
"""
__docformat__ = "restructuredtext en"
_ = lambda x: x

from Tkinter import BooleanVar, Listbox, DoubleVar, IntVar, StringVar
import tkSimpleDialog
import tkColorChooser
import tkFont
import ttk

from . import constants as vcnst
from .window import PAD
from ..settings import CONF_OPT
from ..pysolaudio import AUDIO_DEV
from ..statdb  import STAT_DB


class _ConfigDialog(tkSimpleDialog.Dialog, object):
    """
    Abstract config dialog.
    """
    LABELS = []
    _values = {}
    _option = ''
    _caption = lambda s, v: v
    _returns = lambda s, v: v
    _title = _('Configure Options')
    status = False

    def __init__(self, parent):
        """
        __init__(parent:Canvas) -> None

        Call parent method in **Dialog**.
        """
        tkSimpleDialog.Dialog.__init__(self, parent,
                                       title=self._title)

    def body(self, parent, attribute):
        """
        body(self, parent:Canvas, attribute:str) -> None

        Create a row with title, value with attribute applied, and button for
        each item within **option** from **LABELS**.
        """
        option = CONF_OPT[self._option]
        kwargs = {}

        for row, (title, key) in enumerate(self.LABELS):
            self._values[key] = StringVar()
            self._values[key].set(self._caption(option[key]))

            title = ttk.Label(parent, text=title, anchor=vcnst.ANCHOR_W)
            title.grid(row=row, column=0, sticky=vcnst.STICKY_EW)

            kwargs[attribute] = option[key]
            text = ttk.Label(parent, width=25, textvariable=self._values[key],
                             **kwargs)
            text.grid(row=row, column=1, padx=5)

            bttn = ttk.Button(parent, text=_('Change...'), width=10,
                         command=lambda l=text, k=key: self.select_option(l, k))
            bttn.grid(row=row, column=2)

    def select_option(self, label, key):
        """
        select_option(label:str, key:str) -> None

        Stub to load another dialog and store its value.
        """
        raise NotImplementedError

    def validate(self):
        """
        validate() -> bool

        Check each labels for changed values from item within options.
        """
        option = CONF_OPT[self._option]
        for title, key in self.LABELS:
            if option[key] != self._returns(self._values[key].get()):
                self.status = True
        return True

    def apply(self):
        """
        apply() -> None

        Update item within options with value of its label.
        """
        option = self._option
        for title, key in self.LABELS:
            CONF_OPT[option][key] = self._returns(self._values[key].get())


class ColorsDialog(_ConfigDialog):
    """
    Change line and text color option.
    """
    LABELS = ((_('Text foreground:'), 'text',),
              (_('Highlight piles:'), 'piles',),
              (_('Highlight cards 1:'), 'cards_1',),
              (_('Highlight cards 2:'), 'cards_2',),
              (_('Highlight same rank 1:'), 'samerank_1',),
              (_('Highlight same rank 2:'), 'samerank_2',),
              (_('Hint arrow:'), 'hintarrow',),
              (_('Highlight not matching:,'), 'not_matching',),)
    _option = 'colors'
    _title = _('Configure Color Options')

    def body(self, parent, *args):
        """
        body(self, parent:Canvas) -> None

        Create a row with title, value with attribute applied, and button for
        each item within option. Set attribute for parent as 'bg' to modify
        label background to option value.
        """
        super(ColorsDialog, self).body(parent, 'background')

    def select_option(self, label, key):
        """
        select_option(label:str, key:str) -> None

        Load **ColorChooser.askcolor** to set a new color for label option.
        Update label title with color selected.
        """
        chooser = tkColorChooser.askcolor(self, _("Select color"),
                                          initialcolor=label.cget('bg'))
        if chooser and chooser[1]:
            label.configure(bg=chooser[1])
            label.setvar(label.cget('textvariable'), chooser[1])


SET_SCALE_LABEL = lambda l, v: '{}: {}'.format(l, v)


class FontChooser(tkSimpleDialog.Dialog, object):
    """
    Font chooser to select a font.
    """
    font = None
    _values = {}

    def _get_font(self):
        """
        _get_font() -> seq(family, size [, weight, slant])

        Return translated font seq from current font dict values. This is the
        get method **_font**.
        """
        font = [self._values['family'].get(), self._values['size'].get()]
        if self._values['weight'].get():
            font += ['bold']
        if self._values['slant'].get():
            font += ['italic']
        return font

    def _set_font(self, font=None):
        """
        _set_font(font:seq(family, size [, weight, slant])=None) -> None

        Set translated font dict values from font seq passed. Default sets
        family as Helvetica, size as 12, and both weight and slant as False.
        This is the set method **_font**.
        """
        if font is not None:
            size = len(font)
            assert 2 <= size <= 4
            self._values['family'].set(font[0])
            if isinstance(font[1], int):
                self._values['size'].set(font[1])
            else:
                raise ValueError('invalid font size: {0[1]}'.format(font))
            for value in font[2:]:
                if value in ['bold', 'normal']:
                    self._values['weight'].set(value == 'bold')
                elif value in ['italic', 'roman']:
                    self._values['slant'].set(value == 'italic')
                else:
                    raise ValueError('invalid font style: {0}'.format(value))
        else:
            self._values['family'].set('Helvetica')
            self._values['size'].set(12)
            self._values['weight'].set(False)
            self._values['slant'].set(False)

    _font = property(_get_font, _set_font)

    def __init__(self, parent, title, font):
        """
        __init__(parent:Canvas, title:str, font:seq(family, size [, weight,
          slant])) -> None

        Init font dict and set font passed. Call parent method **Dialog**.
        """
        self._values = {'family': StringVar(),
                        'size': IntVar(),
                        'weight': BooleanVar(),
                        'slant': BooleanVar()}
        self._set_font(font)
        tkSimpleDialog.Dialog.__init__(self, parent, title)

    def body(self, parent):
        """
        body(parent:Canvas) -> None

        Create a font chooser with fields for font properties. Calls method to
        fill listbox with available fonts.
        """
        sample_text = ttk.Entry(parent)
        sample_text.grid(row=0, column=0, columnspan=2, sticky=vcnst.STICKY_NS)
        sample_text.insert('end', _('abcdefghABCDEFGH'))
        self._sample_text = sample_text

        font_choice = Listbox(parent, width=36, exportselection=False)
        scrollbar = ttk.Scrollbar(parent)
        font_choice.configure(yscrollcommand=scrollbar.set)
        scrollbar.configure(command=font_choice.yview)
        font_choice.grid(row=1, column=0, sticky=vcnst.STICKY_NSEW)
        scrollbar.grid(row=1, column=1, sticky=vcnst.STICKY_NS)
        font_choice.bind('<<ListboxSelect>>', self.select_option)
        self._font_choice = font_choice

        cb1 = ttk.Checkbutton(parent, text=_('Bold'),
                              command=self.select_option,
                              variable=self._values['weight'])
        cb1.grid(row=2, column=0, columnspan=2, sticky=vcnst.STICKY_EW)

        cb2 = ttk.Checkbutton(parent, text=_('Italic'),
                              command=self.select_option,
                              variable=self._values['slant'])
        cb2.grid(row=3, column=0, columnspan=2, sticky=vcnst.STICKY_EW)

        frame = ttk.Frame(parent)
        self.label = ttk.Label(frame, anchor=vcnst.ANCHOR_W)
        self.label.pack(side=vcnst.LEFT, expand=True, fill=vcnst.HORZ)
        scale = ttk.Scale(frame, from_=6, to=40, orient=vcnst.HORIZONTAL,
                          command=self.select_option,
                          variable=self._values['size'])
        scale.pack(side=vcnst.LEFT, expand=True, fill=vcnst.BOTH, pady=5)
        frame.grid(row=4, column=0, columnspan=2, sticky=vcnst.STICKY_NSEW)
        self._load_listbox()
        self.select_option()

    def _load_listbox(self):
        """
        _load_listbox() -> None

        Set listbox with available fonts.
        """
        font_families = list(tkFont.families())
        font_families.sort()
        self._font_choice.insert('end', *font_families)
        for i, font in enumerate(font_families):
            if font.lower() == self._values['family'].get().lower():
                self._font_choice.select_set(i)
                self._font_choice.see(i)
                break

    def select_option(self, *args):
        """
        select_option() -> None

        Update font sample to reflect current font selected.
        """
        self.label.configure(text=SET_SCALE_LABEL(_('Size'),
                                                  self._values['size'].get()))
        family_select = self._font_choice.curselection()
        if family_select:
            self._values['family'].set(self._font_choice.get(family_select))
        self._sample_text.configure(font=self._font)

    def apply(self):
        """
        apply() -> None

        Set font with selected font seq.
        """
        self.font = self._font


class FontsDialog(_ConfigDialog):
    """
    Change font options.
    """
    LABELS = (#(_('HTML:'), 'sans',),
              (_('Small:'), 'small',),
              #(_('Fixed:'), 'fixed',),
              (_('Tableau default:'), 'canvas_default',),
              (_('Tableau fixed:'), 'canvas_fixed',),
              (_('Tableau large: '), 'canvas_large',),
              (_('Tableau small: '), 'canvas_small',),)
    _option = 'fonts'
    _caption = lambda s, v: '; '.join(str(t) for t in v)
    _returns = lambda s, v: v.split(';')
    _title = _('Configure Font Options')

    def body(self, parent, *args):
        """
        body(self, parent:Canvas) -> None

        Create a row with title, value with attribute applied, and button for
        each item within option. Set attribute for parent as 'font' to modify
        label font to option value.
        """
        super(FontsDialog, self).body(parent, 'font')

    def select_option(self, label, key):
        """
        select_option(label:str, key:str) -> None

        Load **FontChooser** to set a new font for label option.
        Update label title with font selected.
        """
        chooser = FontChooser(self, _('Select font'),
                              CONF_OPT['fonts'][key])
        if chooser.font:
            label.setvar(label.cget('textvariable'),
                         self._caption(chooser.font))
            label.configure(font=chooser.font)


class TimeoutsDialog(_ConfigDialog):
    """
    Change timeout values for various actions.
    """
    LABELS = ((_('Demo'), 'demo'),
              (_('Hint'), 'hint'),
              (_('Raise card'), 'raise_card'),
              (_('Highlight piles'), 'highlight_piles'),
              (_('Highlight cards'), 'highlight_cards'),
              (_('Highlight same rank'), 'highlight_samerank'),)
    _option = 'timeouts'
    _title = _('Configure Time Out Options')
    _returns = lambda s, v: float('{:.1f}'.format(v / 10))

    def body(self, parent, *args):
        """
        body(self, parent:Canvas) -> None

        Create a row with title, scale with timeout value applied. Do not
        set an attribute for parent method to modify label to option value.
        """
        option = CONF_OPT[self._option]

        for row, (title, key) in enumerate(self.LABELS):
            self._values[key] = DoubleVar()
            self._values[key].set(self._caption(option[key] * 10))

            label = ttk.Label(parent, text=title, anchor=vcnst.ANCHOR_W)
            label.grid(row=row, column=0, sticky=vcnst.STICKY_EW)
            value = ttk.Scale(parent, from_=2, to=99, orient=vcnst.HORIZONTAL,
                              command=lambda e: self.set_scale(row, label),
                              variable=self._values[key])
            value.grid(row=row, column=1, padx=PAD.X, sticky=vcnst.STICKY_EW)
            self.set_scale(row, label)

    def select_option(self, label, key):
        """
        select_option(label:str, key:str) -> None

        Do nothing when called.
        """
        pass

    def set_scale(self, row, label):
        """
        set_scale(row:int, label:str) -> None

        Update label for scale adjustment.
        """
        title, key = self.LABELS[int(row)]
        label.configure(text=SET_SCALE_LABEL(title,
                                        self._returns(self._values[key].get())))


class SoundsDialog(_ConfigDialog):
    """
    Enable sound events for various actions.
    """
    LABELS = ((_('Are You Sure'), 'areyousure',),
              (_('Deal'), 'deal',),
              (_('Deal waste'), 'dealwaste',),
              (_('Turn waste'), 'turnwaste',),
              (_('Start drag'), 'startdrag'),
              (_('Drop'), 'drop',),
              (_('Drop pair'), 'droppair',),
              (_('Auto drop'), 'autodrop',),
              (_('Flip'), 'flip',),
              (_('Auto flip'), 'autoflip',),
              (_('Move'), 'move',),
              (_('No move'), 'nomove',),
              (_('Undo'), 'undo',),
              (_('Redo'), 'redo',),
              (_('Autopilot lost'), 'autopilotlost',),
              (_('Autopilot won'), 'autopilotwon',),
              (_('Game finished'), 'gamefinished',),
              (_('Game lost'), 'gamelost',),
              (_('Game won'), 'gamewon',),
              (_('Perfect game'), 'gameperfect',),)
    _option = 'sound_samples'
    _caption = lambda s, v: _('disable sound') if v else _('enable sound')
    _title = _('Configure Sound Options')

    def body(self, parent, *args):
        """
        body(self, parent:Canvas) -> None

        Create a row with a button for enabling event sounds then create a frame
        containing options to enable specific sound events.
        """
        option = CONF_OPT[self._option]

        widget = ttk.Button(parent, text=self._caption(CONF_OPT['sound']),
                         width=15, command=lambda e: self.select_option(widget))
        widget.grid(row=0, column=0, columnspan=2, padx=PAD.X)

        frame = ttk.LabelFrame(parent, text=_('Enable Event Sounds'))
        frame.grid(row=1, column=0, columnspan=2, padx=PAD.X, pady=PAD.Y)
        for idx, (title, key) in enumerate(self.LABELS):
            self._values[key] = BooleanVar()
            self._values[key].set(self._caption(option[key]))

            check_bttn = ttk.Checkbutton(frame, text=title,
                                         variable=self._values[key])
            check_bttn.grid(row=idx / 2, column=idx % 2, sticky=vcnst.STICKY_EW,
                            padx=PAD.X)

    def select_option(self, widget):
        """
        select_option() -> None

        Do nothing when called.
        """
        CONF_OPT['sound'] = not CONF_OPT['sound']
        widget.config(text=self._caption(CONF_OPT['sound']))

    def apply(self):
        """
        apply() -> None

        Update item within options with value of its label. Update
        **AUDIO_DEV** with new options.
        """
        _ConfigDialog.apply(self)
        if AUDIO_DEV.connected:
            AUDIO_DEV.update()
            AUDIO_DEV._play_sound("drop", priority=1000)


class PlayersDialog(_ConfigDialog):
    """
    Set player and enable misc. game options.
    """
    LABELS = ((_('Confirm quit'), 'confirm',),
              (_('Update stats and logs'), 'update_stats',),
              (_('Animate Win'), 'win_animation',))

    _title = _('Configure Player Options')
    _returns = lambda s, v: v[:30].strip()

    def body(self, parent, *args):
        """
        body(self, parent:Canvas) -> None

        Create a row with a box for player entry or select from past players in
        STAT_DB then create a frame containing options to enable misc. options.
        """
        widget = ttk.Label(parent, text=_("Player name:"), takefocus=False)
        widget.grid(row=0, column=1, sticky='we')
        self._values['player'] = ttk.Combobox(parent, width=30, values=[])
        self._values['player'].grid(row=0, column=1, sticky=vcnst.STICKY_EW,
                                    padx=PAD.X)
        self._load_combobox()

        frame = ttk.LabelFrame(parent, text=_('Game Options'))
        frame.grid(row=1, column=0, columnspan=2, padx=PAD.X, pady=PAD.Y)
        for idx, (title, key) in enumerate(self.LABELS):
            self._values[key] = BooleanVar()
            self._values[key].set(self._caption(key))

            check_bttn = ttk.Checkbutton(frame, text=title,
                                         variable=self._values[key])
            check_bttn.grid(row=idx % 2, column=idx / 2, sticky=vcnst.STICKY_EW,
                            padx=PAD.X)

    def select_option(self, label, key):
        """
        select_option(label:str, key:str) -> None

        Do nothing when called.
        """
        pass

    def _load_combobox(self):
        """
        _load_combobox() -> None

        Set combobox with available fonts.
        """
        names = STAT_DB.players()
        if CONF_OPT['player'] not in names:
            names.append(CONF_OPT['player'])
        names.sort()
        self._values['player'].config(values=names)
        self._values['player'].current(names.index(CONF_OPT['player']))

    def validate(self):
        """
        validate(label:str, key:str) -> None

        Check each labels for changed values from item within options.
        """
        for title, key in self.LABELS:
            returns = self._returns if key == 'player' else lambda x: x
            if CONF_OPT[key] != returns(self._values[key].get()):
                self.status = True
        return True

    def apply(self):
        """
        apply() -> None

        Update item within options with value of its label.
        """
        for title, key in self.LABELS:
            returns = self._returns if key == 'player' else lambda x: x
            CONF_OPT[key] = returns(self._values[key].get())